package com.lzkjiot.service.gnss.transger.core.client;

import com.lzkjiot.service.gnss.transger.core.constants.NettyConstants;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class TestClientPoolManager {

    private static String sinanHost;

    @Value("${sinan.host}")
    public void setSinanHost(String host) {
        sinanHost = host;
    }

    private static EventLoopGroup group;
    private static Bootstrap bootstrap;
    private static ConcurrentHashMap<Integer, Channel> channelMap = new ConcurrentHashMap<Integer, Channel>();


    @PostConstruct
    public void init() {
        log.info("[TransferClient] Client Manager server init host:{}", sinanHost);
        group = new NioEventLoopGroup();
        bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .handler(new TestClientInitializer());
    }

    public static  void pushGnssData(Integer port, byte[] data){
        Channel channel = getConnect(port);
        ByteBuf dataBuf = Unpooled.wrappedBuffer(data);
        if(channel.isWritable()){
            channel.writeAndFlush(dataBuf);
            log.info("[TransferClient] port:{}, push data len:{}", port, data.length);
        }
    }

    public static void removeChannel(Integer port){
        channelMap.remove(port);
    }
    private static Channel connect(Integer port) {
        try {
            log.info("[TransferClient] create connect for port:{}", port);
            Channel channel = bootstrap.connect(sinanHost, port).sync().channel();
            channel.attr(NettyConstants.SERVER_PORT).set(String.valueOf(port));
            channelMap.put(port, channel);
            return channel;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private static Channel getConnect(Integer port){
        if(channelMap.containsKey(port) && channelMap.get(port).isActive()){
            return channelMap.get(port);
        } else {
            return connect(port);
        }
    }


    public void shutdown() {
        group.shutdownGracefully();
    }
}
@Slf4j
class TestClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(new IdleStateHandler(0, 0, 0, TimeUnit.SECONDS));
        ch.pipeline().addLast(new TestClientHandler());
    }
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent e = (IdleStateEvent) evt;
            if (e.state() == IdleState.READER_IDLE) {
                String port = ctx.channel().attr(NettyConstants.SERVER_PORT).get();
                ctx.flush();
                ctx.close();
                SiNanClientPoolManager.removeChannel(Integer.valueOf(port));
                log.error("[TransferClient] port[{}] channel close. Cause idle timeout",port);
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        String port = ctx.channel().attr(NettyConstants.SERVER_PORT).get();
        ctx.flush();
        ctx.close();
        SiNanClientPoolManager.removeChannel(Integer.valueOf(port));
        log.error("[TransferClient] port[{}] channel closeed. Cause exption:{}",port,cause.getMessage());
    }
}

